/**
 * 泛型作用：
 * 动态类型，程序会自动推导出参数的类型，不用写重复性代码
 * @param arg
 * @returns
 */

// 返回值的类型和传入参数的类型相同
function identity<T>(arg: T): T {
  return arg
}
// 我们定义了泛型函数后，可以用两种方法使用。 第一种是，传入所有的参数，包含类型参数：
console.log(identity<string>('字符串1'))
// 第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型：
console.log(identity('字符串2'))

// 使用泛型变量
function getLength1<T>(arg: T[]): T[] {
  console.log('1参数的长度为:', arg.length)
  return arg
}
function getLength2<T>(arg: Array<T>): Array<T> {
  console.log('2参数的长度为:', arg.length)
  return arg
}
getLength1([1, 2, 3, 4, 5])
getLength2([1, 2, 3, 4, 5, '6'])

// 泛型的默认类型
function qq<T = number, K = number>(a: T, b: K): Array<T | K> {
  return [a, b]
}
// qq()  //默认类型为number,number
// qq(false, '1') //此时类型为boolean和string

// 写泛型接口
// interface GenericIdentityFn {
//   <T>(arg: T): T
// }
function identity2<T>(arg: T): T {
  return arg
}
// let genericIdentity: GenericIdentityFn = identity2
// let genericIdentity: GenericIdentityFn = <T>(arg: T): T => {
//   return arg
// }

// console.log('genericIdentity:', genericIdentity('泛型接口'))

// 传入一个类型参数来指定泛型类型（这里是：number），锁定了之后代码里使用的类型
interface GenericIdentityFn2<T> {
  (arg: T): T
}
let genericIdentity2: GenericIdentityFn2<number> = identity2
console.log('genericIdentity2:', genericIdentity2(12321321))

interface GenericIdentityFn3<T> {
  msg: T
}
let genericIdentity3: GenericIdentityFn3<string> = {
  msg: '123'
}
let genericIdentity4: GenericIdentityFn3<boolean> = {
  msg: true
}
console.log('---->', genericIdentity3)

// 泛型类
// 泛型类看上去与泛型接口差不多。 泛型类使用（ <>）括起泛型类型，跟在类名后面。
class GenericNumber<T> {
  zeroValue!: T
  add!: (x: T, y: T) => T
}
let myGenericNumber = new GenericNumber<number>()
myGenericNumber.zeroValue = 0
myGenericNumber.add = function (x, y) {
  return x + y
}
console.log(myGenericNumber.zeroValue)
console.log(myGenericNumber.add(1, 6))

// 并不限制GenericNumber类使用的类型，也可以只用string，如下例：
let myGenericString = new GenericNumber<string>()
myGenericString.zeroValue = '1'
myGenericString.add = function (x, y) {
  return x + y
}
// console.log(myGenericString.add(myGenericString.zeroValue, '2'))

// 泛型约束
interface Lengthwise {
  length: number
  size?: number //可选参数
}
function getLength3<T extends Lengthwise>(arg: T): T {
  console.log(
    '通过泛型约束，继承Lengthwise的length属性，就可以获取到length的值:',
    arg.length
  )
  return arg
}
//传入的参数必须是：符合约束类型的值 且 必须包含必须的属性，如length
console.log(getLength3({ length: 3, value: 'hhhhh' }))
console.log('-----------------------')

// 在泛型约束中使用类型参数     <这里代码存在报错，我不知道怎么解决>
// 已解决:bilibili小满ts合集p18
// 引用数据类型obj记得用keyof
let p = {
  name: 'qq',
  sex: '男'
}
type Key = typeof p

type Key2 = keyof typeof p

function getProperty<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key]
}
let x = { a: 1, b: 2, c: 3, d: 4 }
console.log('getProperty:', getProperty(x, 'c'))
console.log(getProperty(p, 'name'))

// keyof高级用法
// 将接口Data中的属性变成可选形式 ?:
interface Data {
  name: string
  sex: string
  age: number
}
type Options<T extends object> = {
  // 类似于for in 循环
  [Key in keyof T]?: T[Key]
}
type D = Options<Data>
// 结果：
// type D = {
//   name?: string | undefined;
//   sex?: string | undefined;
//   age?: number | undefined;
// }
/**
 * 泛型实战--xhr请求
 */

const axios = {
  get: function <T>(url: string): Promise<T> {
    return new Promise((resolve, reject) => {
      let xhr: XMLHttpRequest = new XMLHttpRequest()
      xhr.open('GET', url)
      xhr.onprogress = function (env) {
        console.log(
          '请求进度:',
          ((env.loaded / env.total) * 100).toFixed(2) + '%'
        )
      }
      xhr.onreadystatechange = function () {
        console.log('xhr:', xhr)
        if (xhr.status == 200 && xhr.readyState == 4) {
          resolve(JSON.parse(xhr.responseText))
        }
      }
      xhr.send(null)
    })
  }
  // 写法二：
  // get<T>(url:string):Promise<T>{
  //   return new Promise((resolve,reject)=>{

  //   })
  // }
}

// 需要定义接口，给get类型，不然为unknow。获取不了res的属性值
interface data {
  message: string
  dateCode: number
}
axios.get<data>('./data.json').then((res) => {
  res.a
  // console.log('res:', res.message)
  // console.log('res:', res.dateCode)
})
